跳到主要内容

Git 打磨 commit 提交~

移动部分提交到当前分支

对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。

这时分两种情况。一种情况是,你需要另一个分支的所有代码变动,那么就采用合并(git merge)。另一种情况是,你只需要部分代码变动(某几个提交),这时可以采用 Cherry pick。

即将一些提交复制到当前所在的位置(HEAD)下面的

例如这里有一个仓库, 我们想将 side 分支上的工作复制到 master 分支,你立刻想到了之前学过的 rebase 了吧?但是咱们还是看看 cherry-pick 有什么本领吧。

执行这个 cherry-pick 命令

git cherry-pick c2 c4

我们只需要提交记录 C2 和 C4,所以 Git 就将被它们抓过来放到当前分支下了。 就是这么简单!

下面再来一个例子

执行下面的命令把各个 commit 抓取到 master 分支下面

git cherry-pick c3 c4 c7

交互式 Rebase 整理提交

当你知道你所需要的提交记录(并且还知道这些提交记录的哈希值)时,用 cherry-pick 再好不过了(这个命令下面会讲) —— 没有比这更简单的方式了。

但是如果你不清楚你想要的提交记录的哈希值呢? 幸好 Git 帮你想到了这一点,我们可以利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录,这就是最好的方法了

交互式 rebase 指的是使用带参数 --interactiverebase 命令,简写为 -i

rebase -i 叫交互式 rebase ,那它交互的是什么呢?是提交。

rebase -i 的第一步,就是选定交互的提交,或者说提交们,这是一个范围。

例如下面的命令,就是选定 [ 当前提交,当前往前数三个提交 ),这是一个左闭右开的集合。

git rebase -i HEAD^^^··

一个 enter 按下去,就进入了 rebase 交互的页面,如下图:

然后可以修改下这个提交信息

这里解释一下上面的几个命令

p,pick使用该提交,也是默认操作,这个从上面的编辑区域可以看出。这个命令的含义是拿到这个命令,但是什么都不做。

r,reword拿到提交,修改提交的提交信息。

e,edit拿到提交,修改这个提交的内容。使用这个命令的时候,rebase 操作会停在操作提交处,等待修改完毕,使用 git add .git commit --amend 修改提交,git rebase --continue 继续 rebase 进程。

s,squash:*这个命令很厉害的,可以将使用这个命令的提交 与它的父提交融合为一个提交。*

f,fixupsquash 命令的作用一样,不同的是,squash 命令会把融合的提交的提交信息都保存在融合后的提交信息中,但是 fixup 会直接放弃被融合的提交信息,只保留父节点的信息。

d,drop删除提交

知道了命令,现在看怎么用:

编辑操作命令

确定要操作的 commit,确定操作命令后,将目标提交的 pick 修改成成对应的操作命令。不需要处理的默认 pick 就好,不要删除,删除的意思是要移除这个提交,和 drop 是一个意思。

需要明白的是,编辑区默认的提交顺排列序是按提交历史上的顺序排列的,由前到后,顺序执行,所以顺序不要颠倒。不然你想想,改完最后一个提交之后,再修改第一个提交,历史都散开不能串成一串了。

注意:这个修改命令只需在前面加上命令就行了,无需更改后面已有的 message,因为等下进入的那个 vim 界面才是让你修改 message!!

可以发现换成这个 r 后保存 :wq 会自动进入这个新窗口,它就是让你修改这个 commited 的 message

使用 git log 可以看到这些 message 修改了

解决冲突

保存退出交互页面就开始执行 rebase 了。顺利的话,直接执行成功,发生冲突,解决之后,执行 git rebase --continue 继续进行,直至执行成功。

修改提交粒度

关于 commit 的颗粒度,提交频率高,就细一点,例如修改了一个文件就打个提交;提交频率低,就粗一点,例如这整个模块完成了,才有一次最终版本的提交。

因为真实开发中,有太多没有一点意义提交了,放张刚翻出来的截图,这是远程仓库中一个项目的部分提交历史。

这个提交毫无意义,看的人,根本不知道你提交了些什么,非常不利于后期维护。合理的做法是,推动在远程之前,现在在自己的本地分支上把提交历史理清楚。

例如下图,就可以用 rewordfixup 命令完成从前到后的变化。

下面开始构建测试场景

# 切换到GitDemo目录下,并初始化Git
cd ../GitDemo
git init

# 创建初次提交 C1
touch rebase.txt
git add .
git commit -m '初次提交'


# 创建提交 C2
git checkout -b develop
touch A.txt
git add .
git commit -m "详情页布局完成一半"


# 创建提交 C3
touch B.txt
git add .
git commit -m "详情页布局结束"

# 创建提交 C4
touch C.txt
git add .
git commit -m "修复了2个bug"

分析:

  1. 改动范围有 C2,C3,C4,确定使用 HEAD^^^
  2. 要达到如上场景,要把 C2 和 C3 合并为一个提交 C5,fixupsquash 满足要求,但 C5 的提交未保存 C3 的信息,那确定 C3 使用 fixup 操作。
  3. C5 的提交信息 C2 和 C3 都不同,那么 C2 需要先把提交信息改成合并后的提交信息,C2 需要 reword 操作来修改提交信息。
  4. C4 的提交信息修改,C4 需要 reword 操作。
# 开始 rebase -i
git rebase -i HEAD^^^

注:截图这里的 202af4e 也应该设为 reword,但是这里忘记了~

执行成功,现在通过 git log 查看操作分支的历史,和操作前的对比,确认已经成功整理了 commit 历史,大功告成!同时也注意到 rebase 后的提交 SHA1 值发生变化,证明是一个新的提交。

如下图所示(借作者原图):

本地栈式提交

来看一个在开发中经常会遇到的情况:我正在解决某个特别棘手的 Bug,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。

这些调试和打印语句都在它们各自的提交记录里。最后我终于找到了造成这个 Bug 的根本原因,解决掉以后觉得沾沾自喜!

最后就差把 bugFix 分支里的工作合并回 master 分支了。你可以选择通过 fast-forward 快速合并到 master 分支上,但这样的话 master 分支就会包含我这些调试语句了。你肯定不想这样,应该还有更好的方式……

实际我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在 “整理提交记录” 中学到的一样,我们可以使用

  • git rebase -i
  • git cherry-pick

git checkout master
git cherry-pick c4

References